/**
 * Alessandro Crugnola
 * http://www.sephiroth.it
 * 
 * alessandro@sephiroth.it
 * 
 * Modified by Kinght-errant
 * http://www.godpaper.com
 *
 * youngwelle@gmail.com 
 */

package it.sephiroth.controls
{
	import flash.display.Bitmap;
	import flash.display.BitmapData;
	import flash.display.GradientType;
	import flash.display.Graphics;
	import flash.display.Loader;
	import flash.display.LoaderInfo;
	import flash.display.PixelSnapping;
	import flash.display.Shape;
	import flash.display.SpreadMethod;
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.events.IOErrorEvent;
	import flash.events.MouseEvent;
	import flash.geom.Matrix;
	import flash.net.URLRequest;
	
	import it.sephiroth.events.BitmapScrollerEvent;
	
	import mx.core.UIComponent;
	
	import org.as3lib.kitchensync.action.KSTween;
	import org.as3lib.kitchensync.core.KitchenSyncEvent;
	import org.as3lib.kitchensync.core.Synchronizer;
	import org.as3lib.kitchensync.easing.Bounce;
	import org.as3lib.kitchensync.easing.Quintic;

	[Event(name="complete", type="flash.events.Event")]
	[Event(name="change", type="flash.events.Event")]

	public class BitmapScroller extends UIComponent
	{
		private var _loaderData: Array;
		private var _dataProvider: Array;
		private var _dataProviderChanged: Boolean;
		
		private var _background: Shape;
		private var _container: Sprite;
		private var _mask: Shape;
		private var _window: Shape;
		private var _valid: Boolean;
		
		private var _width: Number  = 72;
		private var _height: Number = 95;

		private var box_height: Number = 100;
		private var max_y: Number;
		private var min_y: Number;
		private var space: Number = 0;

		private var loader: Loader;
		private var anchor: Number;
		private var down: Boolean;
		private var velocity: Number;
		public var tween: KSTween;
		private var prev_value: Number = NaN;

		// options		
		public static var BG_COLOR: uint = 0xFFFFFF;
		public static var CORNER: uint = 4;
		public static var HIGHTLIGHT_COLOR_BOTTOM: uint = 0x000000;
		public static var HIGHTLIGHT_COLOR_TOP: uint    = 0x000000;
		public static var ACCELERATION: uint = 5;
		public static var MIN_DURATION: uint = 500;
		public static var EASING: Function = Quintic.easeInOut;
		public static var MAX_WIDTH: uint  = 72;
		public static var MAX_HEIGHT: uint = 95;
		
		public function BitmapScroller( )
		{
			super( );
			addEventListener( Event.ADDED_TO_STAGE, onAddedToStage );
			addEventListener( Event.REMOVED_FROM_STAGE, onRemoveFromStage );
			
			anchor   = 0;
			velocity = 0;
			
			createChildren( );

			tween = new KSTween( _container, "y", 0, 0, 0, 0, EASING );
			tween.snapToValueOnComplete = true;
//			tween.snapToWholeNumber = true;//120
			
			loader = new Loader( );
			loader.contentLoaderInfo.addEventListener( Event.COMPLETE, onLoaderComplete );
			loader.contentLoaderInfo.addEventListener( IOErrorEvent.IO_ERROR, onLoaderError );
		}
		
		public function setSize( w: Number, h: Number ): void
		{
			_width  = w;
			_height = h;
		}
		
		public function setBitmapSize( w: Number, h: Number ): void
		{
			box_height = h;
			space = ( box_height - _height ) / 2;
		}
		
		public function set dataProvider( value: Array ): void
		{
			_dataProvider = value;
			_loaderData   = value.concat( );
			_dataProviderChanged = true;
			invalidateDataProvider( );
		}
		
		override protected function createChildren( ): void
		{
			if( !_background )
			{
				_background = new Shape( );
				_container  = new Sprite( );
				_mask 		= new Shape( );
				_window 	= new Shape( );
				
				addChild( _background );
				addChild( _container );
				addChild( _mask );
				addChild( _window );
				
				_container.mask = _mask;
				
				_mask.cacheAsBitmap = true;
				_container.cacheAsBitmap = true;
			}
		}
		
		
		protected function invalidate( ): void
		{
			var dest_y:Number = _container.y + velocity * ACCELERATION;
			var diff_y:Number = ( dest_y % box_height );
			
			dest_y -= diff_y;
			if( Math.abs( diff_y + space ) > box_height / 2 ) dest_y += _container.y > 0 ? box_height : -box_height;
			
			if( dest_y < 0 ) dest_y = Math.max( dest_y, - ( box_height * ( _dataProvider.length - 1 ) ) );
			else dest_y = Math.min( 0, dest_y );
			
			tween.name = 'user';
			tween.easingFunction = EASING;
			tween.tweenTarget.startValue = _container.y;
			tween.tweenTarget.endValue = dest_y - space;
			tween.duration = MIN_DURATION + Math.abs( velocity ) * ACCELERATION;
			tween.start( );
		}
		
		protected function onTweenUpdate( event: KitchenSyncEvent ): void
		{
			if( tween.isRunning )
			{
				var next_value: Number = tween.tweenTarget.currentValue;
				if( !isNaN( prev_value ) )
				{
					if( next_value % box_height == 0 )
					{
						if( next_value == tween.tweenTarget.endValue )
						{
							tween.stop( );
							onComplete( null );
						}
					}
				}
				prev_value = next_value;
			}
		}
		
		private function loadNext( ): void
		{
			if( _loaderData.length > 0 )
			{
				var url: String = _loaderData.shift( ).toString( );
				try
				{
					loader.load( new URLRequest( url ) );
				} catch( error: Error )
				{
					onLoaderComplete_Post( new BitmapData( _width, box_height, false, 0x000000 ) );
				}
			} else {
				loadComplete( );
			}
		}
		
		private function onLoaderError( event: IOErrorEvent ): void
		{
			onLoaderComplete_Post( new BitmapData( _width, box_height, false, 0x000000 ) );
		}
		
		private function onLoaderComplete( event: Event ): void
		{
			onLoaderComplete_Post( (( event.target as LoaderInfo ).content as Bitmap ).bitmapData );
		}
		
		private function onLoaderComplete_Post( b: BitmapData ): void
		{
			var bmp: Bitmap = new Bitmap( );
			bmp.pixelSnapping = PixelSnapping.ALWAYS;
			bmp.smoothing = true;
			
			var copy: BitmapData = new BitmapData( _width, box_height, false, 0xCCCCCC );
			var matrix: Matrix = new Matrix( 1, 0, 0, 1, 0, 0 );
			var r: Number = 1;
			
			if( b.width > MAX_WIDTH || b.height > MAX_HEIGHT )
			{
				r = MAX_WIDTH / b.width;
				
				if( ( b.height * r ) > MAX_HEIGHT )
				{
					r *= ( MAX_HEIGHT / ( b.height * r ) )
				}
				
				matrix.scale( r, r );
				
			}
			matrix.translate( ( _width - ( b.width * r ))/2, ( box_height - ( b.height * r ))/2 );
			
			copy.draw( b, matrix, null, null, null, true );
			bmp.bitmapData = copy;
			_container.addChild( bmp );
			
			bmp.y = ( box_height * ( _container.numChildren - 1 ) );
			
			_container.y = _dataProvider.length * box_height;
			
			loadNext( );
		}
		
		private function loadComplete( ): void
		{
			trace("BitmapScroller loadComplete!");
			_valid = true;
//			dispatchEvent( new Event( Event.COMPLETE ) );
			dispatchEvent( new BitmapScrollerEvent(BitmapScrollerEvent.COMPLETE) );
		}
		
				
		public function start( ): void
		{
			max_y = -( _dataProvider.length * box_height );
			min_y = -space;
			_container.y = max_y;
			
			tween.name = 'intro';
			tween.easingFunction = Bounce.easeOut;
			tween.tweenTarget.startValue = _container.y;
			tween.tweenTarget.endValue = min_y;    
			/* tween.tweenTarget.startValue = 0;
			tween.tweenTarget.endValue = _dataProvider.length*_height;  */
			tween.duration = _dataProvider.length * 300;
//			tween.duration = 30000;
			tween.start();
		}
		
		/**
		 * Create all the assets for the new dataprovider
		 * 
		 */
		protected function invalidateDataProvider( ): void
		{
			if( tween.isRunning ) tween.stop( );
			_valid = false;
			
			try
			{
				loader.close( );
			} catch( e: Error ){
				trace("About Stream Error:"+ e );
			}
			_container.y = 0;
			
			while( _container.numChildren )
				_container.removeChildAt( 0 );
			
			if( _dataProvider && _dataProvider.length > 0 && stage != null )
			{
				loadNext( );
			}
		}

		/**
		 * This component has been added
		 * to the stage
		 * 
		 */		
		private function onAddedToStage( event: Event ): void
		{
			trace("BitmapScroller onAddedToStage!");
			//KitchenSync.initialize( this );
			Synchronizer.getInstance().addEventListener( KitchenSyncEvent.UPDATE, onTweenUpdate );
			
			invalidateDataProvider( );
			draw( );
			
			addEventListener( MouseEvent.MOUSE_DOWN, onMouseDown );
			tween.addEventListener( KitchenSyncEvent.COMPLETE, onComplete );
			trace("tween: "+tween);
		}
		
		/**
		 * This component has been removed
		 * from the stage
		 *
		 */
		private function onRemoveFromStage( event: Event ): void
		{
			trace("BitmapScroller onRemoveFromStage!");
			removeEventListener( MouseEvent.MOUSE_DOWN, onMouseDown );
			tween.addEventListener( KitchenSyncEvent.COMPLETE, onComplete );
		}
		
		private var _velocities: Array;
		
		private function onMouseDown( event: MouseEvent ): void
		{
			trace("tween.isRunning:"+tween.isRunning);
			if( tween.isRunning )
				tween.pause( );
			
			if( !_valid ) return;
			
			_velocities = new Array();
			
			velocity = 0;
			down     = true;
			anchor   = this.mouseY;
			
			stage.addEventListener( Event.ENTER_FRAME,   onEnterFrame );
			stage.addEventListener( MouseEvent.MOUSE_UP, onMouseUp );
		}
		
		private function onEnterFrame( event: Event ): void
		{
			_velocities.push( this.mouseY - anchor );
			
			_container.y += this.mouseY - anchor;
			anchor = this.mouseY;
		}
		
		private function onMouseUp( event: MouseEvent ): void
		{
			down = false;
			stage.removeEventListener( Event.ENTER_FRAME,   onEnterFrame );
			stage.removeEventListener( MouseEvent.MOUSE_UP, onMouseUp );
			velocity = this.mouseY - anchor;
			prev_value = NaN;
			
			var sum: Number = 0;
			
			for each( var v: Number in _velocities )
			{
				sum += v;
			}
			
			velocity = ( sum / _velocities.length );
			if( isNaN( velocity ) )
				velocity = 0;
			
			invalidate( );
		}
		
		private function onComplete( event: KitchenSyncEvent ): void
		{
			prev_value = NaN;
			trace("(event && ( event.target as KSTween ).name == 'intro')="+event && ( event.target as KSTween ).name == 'intro');
			if( event && ( event.target as KSTween ).name == 'intro' ) return;
			
//			dispatchEvent( new Event( Event.CHANGE ) );
			dispatchEvent( new BitmapScrollerEvent(BitmapScrollerEvent.CHANGE) );
		}
		
		public function get selectedIndex( ): int
		{
			return Math.abs(((_container.y + space ) / box_height ) );
		}
		
		public function set selectedIndex( val: int ):  void
		{
			// set the current value
			val = Math.min( Math.max( val, 0 ), _dataProvider.length - 1 );
			_container.y = -( box_height * val ) - space;
		}
		
		
		/**
		 * draw this component essential components
		 * such background, mask...
		 * 
		 */
		private function draw( ): void
		{
			var g: Graphics = _background.graphics;
			
			// background
			g.clear( );
			g.beginFill( BG_COLOR, 1 );
			g.drawRoundRect( 0, 0, _width, _height, CORNER, CORNER );
			g.endFill( )
			
			// mask
			g = _mask.graphics;
			g.clear( );
			var matrix: Matrix = new Matrix( );
			matrix.createGradientBox( _width, _height, 90*( Math.PI/180 ), 0, 0 );
			g.beginGradientFill( 
				GradientType.LINEAR, 
				[ 0, 0, 0, 0 ],
				[ 0, 1, 1, 0 ], 
				[ 0, 40, 215, 255 ], 
				matrix, 
				SpreadMethod.PAD );
			g.drawRoundRect( 0, 0, _width, _height, CORNER, CORNER );
			g.endFill( );
			
			// window
			g = _window.graphics;
			g.clear( );
			matrix = new Matrix( );
			matrix.createGradientBox( _width, _height, 90*( Math.PI/180 ), 0, 0 );
			g.beginGradientFill( 
				GradientType.LINEAR, 
				[ HIGHTLIGHT_COLOR_TOP, 0xFFFFFF, 0xFFFFFF, 0xFFFFFF, HIGHTLIGHT_COLOR_BOTTOM ],
				[ 0.2, 0.1, 0.0, 0.1, 0.2 ], 
				[ 0, 80, 127, 174, 255 ], 
				matrix, 
				SpreadMethod.PAD );
			g.drawRoundRect( 0, 0, _width, _height, CORNER, CORNER );
			g.endFill( );
		}
	}
}
